/*
 * FILE: rdt_sender.cc
 * DESCRIPTION: Reliable data transfer sender.
 * NOTE: This implementation assumes there is no packet loss, corruption, or 
 *       reordering.  You will need to enhance it to deal with all these 
 *       situations.  In this implementation, the packet format is laid out as 
 *       the following:
 *       
 *       |<-   4 byte   ->|<-   4 byte   ->|<-   4 byte   ->|<-  the rest  ->|
 *       | payload number |  payload size  |    checksum    |     payload    |
 *
 *       The first byte of each packet indicates the size of the payload
 *       (excluding this single-byte header)
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "rdt_struct.h"
#include "rdt_sender.h"

uint crctable[256];
bool mktable = 0;

void maketable()
{
    mktable = 1;
    for(int i = 0;i < 256;i++)
    {
        uint c = (uint)i;
        for(int bit = 0;bit < 8;bit++)
        if(c&1)
            c = (c >> 1)^(0xedb88320);
        else
            c = c >> 1;
        crctable[i] = c;
    }
}

void put(int num, int pos, packet *pkt)
{
    pkt -> data[pos]   = num & 0xff000000;
    pkt -> data[pos+1] = num & 0xff0000;
    pkt -> data[pos+2] = num & 0xff00;
    pkt -> data[pos+3] = num & 0xff;
}

int get(int pos, packet *pkt)
{
    int ans = pkt -> data[pos];
    ans = (ans<<8) | (pkt -> data[pos+1]);
    ans = (ans<<8) | (pkt -> data[pos+2]);
    ans = (ans<<8) | (pkt -> data[pos+3]);
    return ans;
}

int crc32(packet* pkt)
{
    int res = ~0;
    for(int i = 0;i < RDT_PKTSIZE;i++)
    {
        if(i < 12 && i >= 8) continue;
        int m = pkt -> data[i];
        res = (res >> 8) ^ crctable[(res ^ m) & 0xff];
    }
    return ~res;
}

/* sender initialization, called once at the very beginning */
void Sender_Init()
{
    if(!mktable) maketable();
    fprintf(stdout, "At %.2fs: sender initializing ...\n", GetSimulationTime());
}

/* sender finalization, called once at the very end.
   you may find that you don't need it, in which case you can leave it blank.
   in certain cases, you might want to take this opportunity to release some 
   memory you allocated in Sender_init(). */
void Sender_Final()
{
    mktable = 0;
    fprintf(stdout, "At %.2fs: sender finalizing ...\n", GetSimulationTime());
}

std::map<int, packet> go_pkt; // max size 16
std::queue<packet> remain_pkt;

/* event handler, called when a message is passed from the upper layer at the 
   sender */
void Sender_FromUpperLayer(struct message *msg)
{
    /* 12-byte header indicating the size of the payload */
    int maxpayload_size = RDT_PKTSIZE - RDT_HEADERSIZE;

    int num = 1;
    packet pkt;
    int cursor = 0;

    while (msg->size-cursor > maxpayload_size) 
    {
        /* fill in the packet */
        put(num, 0, &pkt);
        put(maxpayload_size, 4, &pkt);
        put(crc32(&pkt), 8, &pkt);

	    memcpy(pkt.data+RDT_HEADERSIZE, msg->data+cursor, maxpayload_size);
        remain_pkt.push(pkt);
    
        num++;
	    cursor += maxpayload_size;
    }

    if (msg->size > cursor) 
    {
	    /* fill in the packet */
        put(num, 0, &pkt);
        put(msg->size-cursor, 4, &pkt);
        put(crc32(&pkt), 8, &pkt);

	    memcpy(pkt.data+RDT_HEADERSIZE, msg->data+cursor, msg->size-cursor);

	    /* send it out through the lower layer */
        remain_pkt.push(pkt);
    }
        
    for(int i = 1;i <= 16 && !remain_pkt.empty();i++)
    {
        auto this_pkt = remain_pkt.front();
        remain_pkt.pop();
        go_pkt.insert(std::make_pair(get(0, &this_pkt), this_pkt));
        Sender_ToLowerLayer(&this_pkt);
    }
    Sender_StartTimer(0.3);
    
}

/* event handler, called when a packet is passed from the lower layer at the 
   sender */
void Sender_FromLowerLayer(struct packet *pkt)
{
    if(get(8, pkt) != crc32(pkt)) return;
    int num = get(0, pkt);
    auto it = go_pkt.find(num);
    if(it == go_pkt.end()) return;
    go_pkt.erase(it);
}

/* event handler, called when the timer expires */
void Sender_Timeout()
{
    while(go_pkt.size() < 16 && !remain_pkt.empty())
    {
        auto this_pkt = remain_pkt.front();
        remain_pkt.pop();
        go_pkt.insert(std::make_pair(get(0, &this_pkt),this_pkt));
    }
    for(auto it = go_pkt.begin();it != go_pkt.end();it++)
    {
        Sender_ToLowerLayer(&(it->second));
    }
    Sender_StartTimer(0.3);
}
